package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.client.course.CategoryClient;
import com.tianji.api.dto.course.CategoryBasicDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.print.attribute.HashAttributeSet;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author LYQ
 * @since 2024-07-03
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
    private final ICouponScopeService scopeService;
    private final CategoryClient categoryClient;
    private final IExchangeCodeService codeService;
    private final IUserCouponService userCouponService;

    /**
     * 新增优惠券
     *
     * @param couponFormDTO
     */
    @Override
    public void addCoupons(CouponFormDTO couponFormDTO) {
        //1、转为po直接保存，表单未填写字段都有默认值，部分属性为发放优惠券时指定
        Coupon newCoupon = BeanUtils.copyBean(couponFormDTO, Coupon.class);
        save(newCoupon);

        //2、判断新增的优惠券是否指定了范围
        if (!newCoupon.getSpecific()) {
            //3、没有指定范围则不用添加优惠券对应业务关系表
            return;
        }
        //4、如果指定，遍历提交的关联业务id集合，批量保存到couponScope表
        List<Long> scopes = couponFormDTO.getScopes();
        if (ObjectUtils.isEmpty(scopes)) {
            return;
        }
        Long couponId = newCoupon.getId();
        List<CouponScope> couponScopeList = scopes.stream().map(bizId ->
                new CouponScope().setCouponId(couponId).setBizId(bizId)
        ).collect(Collectors.toList());
        scopeService.saveBatch(couponScopeList);
    }

    /**
     * 分页查询优惠券
     *
     * @param couponQuery
     */
    @Override
    public PageDTO<CouponPageVO> queryCouponsPage(CouponQuery couponQuery) {

        //1、获取分页条件
        Integer type = couponQuery.getType();
        Integer status = couponQuery.getStatus();
        String name = couponQuery.getName();
        //2、使用lambdaQuery查询分页结果
        Page<Coupon> pageResult = lambdaQuery()
                .eq(ObjectUtils.isNotEmpty(type), Coupon::getType, type)
                .eq(ObjectUtils.isNotEmpty(status), Coupon::getType, status)
                .like(ObjectUtils.isNotEmpty(name), Coupon::getType, name)
                .page(couponQuery.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = pageResult.getRecords();
        if (ObjectUtils.isEmpty(records)) {
            return PageDTO.empty(pageResult);
        }
        //3、转为Vo
        //由于Vo是Po的被包含关系，所以直接转Vo集合即可
        List<CouponPageVO> couponPageVOS = BeanUtils.copyToList(records, CouponPageVO.class);
        //4、封装返回
        return PageDTO.of(pageResult, couponPageVOS);
    }

    /**
     * 管理端 - 根据Id查询优惠券
     *
     * @param id
     */
    @Override
    public CouponDetailVO queryCouponById(Long id) {
        //1、根据Id拆查询
        Coupon coupon = getById(id);
        //2、转Vo
        CouponDetailVO couponDetailVO = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        if (ObjectUtils.isEmpty(couponDetailVO)) {
            return null;
        }
        //3、填充数据
        //3.1、查询范围列表
        if (coupon.getSpecific()) {
            List<CouponScope> scopeList = scopeService.lambdaQuery()
                    .eq(CouponScope::getCouponId, id)
                    .list();
            //3.2转为CouponScopeVo
            if (ObjectUtils.isNotEmpty(scopeList)) {
                //3.2.1收集bizId
                Set<Long> categoryIds = scopeList.stream().map(CouponScope::getBizId).collect(Collectors.toSet());
                //3.2.2远程查询课程分类信息
                List<CategoryBasicDTO> categoryInfoList = categoryClient.queryByIds(categoryIds);
                if (ObjectUtils.isNotEmpty(scopeList)) {
                    //3.2.3、转为volist存入couponDetailVO
                    List<CouponScopeVO> scopeVOS = BeanUtils.copyToList(categoryInfoList, CouponScopeVO.class);
                    couponDetailVO.setScopes(scopeVOS);
                }
            }
        }
        //4、返回Vo
        return couponDetailVO;
    }

    /**
     * 管理端 - 发放优惠券
     *
     * @param dto
     */
    @Override
    public void issueCouponById(CouponIssueFormDTO dto) {
        //1、判断优惠券是否存在
        Coupon coupon = getById(dto.getId());
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在！");
        }
        //2、判断优惠券状态是否为暂停中或者带发放
        if (coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != CouponStatus.PAUSE) {
            throw new BadRequestException("优惠券状态错误！");
        }
        //3、判断是否立刻发放
        LocalDateTime issueBeginTime = coupon.getIssueBeginTime();
        LocalDateTime now = LocalDateTime.now();
        boolean isBegin = issueBeginTime != null && issueBeginTime.isAfter(now);
        //4、更新优惠券表信息
        //4.1拷贝数据到po
        Coupon newCoupon = BeanUtils.copyBean(dto, Coupon.class);
        if (isBegin) {
            //4.1、如果是立即发放，则更新发放开始时间
            newCoupon.setIssueBeginTime(now);
            newCoupon.setStatus(CouponStatus.ISSUING);
        } else {
            //4.2如果不是则不用修改发放起止时间，状态改为带发放
            newCoupon.setStatus(CouponStatus.UN_ISSUE);
        }
        //4.3、写入数据库
        updateById(newCoupon);
        // 5.判断是否需要生成兑换码，优惠券类型必须是兑换码，优惠券状态必须是待发放
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT) {
            coupon.setIssueEndTime(newCoupon.getIssueEndTime());
            codeService.asyncGenerateCode(coupon);
        }
    }

    /**
     * 管理端 - 修改优惠券信息
     *
     * @param dto
     */
    @Override
    @Transactional
    public void updateCouponById(CouponFormDTO dto) {
        //1、查询旧优惠券信息
        //校验：只有处于待发放的优惠券才可以修改
        Coupon oldCoupon = getById(dto.getId());
        if (ObjectUtils.isEmpty(oldCoupon)) {
            throw new BadRequestException("优惠券不存在！");
        }
        if (!(oldCoupon.getStatus() == CouponStatus.DRAFT)) {
            throw new BadRequestException("优惠券状态错误！");
        }
        //2、转为po直接更新，表单未填写字段都有默认值，部分属性为发放优惠券时指定
        Coupon newCoupon = BeanUtils.copyBean(dto, Coupon.class);
        updateById(newCoupon);
        //3、判断原有的优惠券是否指定了范围
        if (!oldCoupon.getSpecific() && !newCoupon.getSpecific()) {
            //4、修改前后都没有指定范围则不用修改优惠券对应业务关系表
            return;
        }
        //4、如果旧优惠券指定了范围，则删除优惠券对应业务关系表
        if (oldCoupon.getSpecific()) {
            scopeService.removeByCouponId(dto.getId());
        }
        //5、如果修改后的优惠券制定了范围，遍历提交的关联业务id集合，批量保存到couponScope表
        List<Long> scopes = dto.getScopes();
        if (ObjectUtils.isEmpty(scopes)) {
            return;
        }
        Long couponId = newCoupon.getId();
        List<CouponScope> couponScopeList = scopes.stream().map(bizId ->
                new CouponScope().setCouponId(couponId).setBizId(bizId)
        ).collect(Collectors.toList());
        scopeService.saveBatch(couponScopeList);
    }

    /**
     * 管理端 - 删除优惠券信息
     *
     * @param id
     */
    @Override
    public void deleteCouponById(Long id) {
        //1、查询旧优惠券信息
        //校验：只有处于待发放的优惠券才可以修改
        Coupon coupon = getById(id);
        if (ObjectUtils.isEmpty(coupon)) {
            throw new BadRequestException("优惠券不存在！");
        }
        if (!(coupon.getStatus() == CouponStatus.DRAFT)) {
            throw new BadRequestException("优惠券状态错误！");
        }
        //2、删除优惠券
        removeById(id);
        //3、判断优惠券是否绑定了课程范围
        if (coupon.getSpecific()) {
            //4、绑定了范围则删除对应优惠券id的绑定关系表数据
            scopeService.removeByCouponId(id);
        }
    }

    /**
     * 管理端 - 暂停优惠券信息
     *
     * @param id
     */
    @Override
    public void pauseCouponById(Long id) {
        //1、查询对应id优惠券
        Coupon coupon = getById(id);
        if (ObjectUtils.isEmpty(coupon)) {
            throw new BadRequestException("优惠券不存在！");
        }
        //2、判断优惠券状态
        CouponStatus status = coupon.getStatus();
        if (status != CouponStatus.ISSUING) {
            throw new BadRequestException("优惠券状态错误！");
        }
        //3、修改优惠券状态为暂停中
        updateById(new Coupon().setStatus(CouponStatus.PAUSE));
    }

    /**
     * 客户端 - 查询发放中的优惠券
     */
    //关键点一、优惠券是否已经被领取完
    //关键点二、用户领取数量是否达到领取限额
    @Override
    public List<CouponVO> queryCoupons() {
        //1、查询发放中的所有优惠券
        List<Coupon> coupons = lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if (ObjectUtils.isEmpty(coupons)) {
            return CollUtils.emptyList();
        }
        //2、查询用户对应当前列表优惠券
        //2.1、收集couponIds
        List<Long> couponIds = coupons.stream().map(Coupon::getId).collect(Collectors.toList());
        Long userId = UserContext.getUser();
        List<UserCoupon> userCoupons = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        //2.2、统计用户对应优惠券的领回数量
        Map<Long, Long> userCouponMap = userCoupons.stream().collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //2.3、统计用户已经领取并且未使用的数量
        Map<Long, Long> usedNumMap = userCoupons.stream().filter(uc -> uc.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //3、转为Vo
        List<CouponVO> voList = new ArrayList<>(coupons.size());
        //4、根据用户与优惠券之间的关系信息填充属性
        for (Coupon coupon : coupons) {
            //4.1 拷贝属性
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);
            voList.add(vo);
            //4.2 判断是否可以领取：已领取优惠券<总发放数量 && 用户领取数量< 限令数量
            boolean available = coupon.getIssueNum() < coupon.getTotalNum() && userCouponMap.getOrDefault(coupon.getId(), 0L) < coupon.getUserLimit();
            //4.3 判断是否可以使用 ：用户拥有该优惠券 && 优惠券状态为未使用的数量>0
            boolean received =usedNumMap.getOrDefault(coupon.getId(), 0L) > 0;
            //4.4填充该属性到vo
            vo.setAvailable(available);
            vo.setReceived(received);
        }
        //available是否可以领取、received是否可以使用
        return voList;
    }
}
